﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;
using NUnit.Framework;

namespace CodeGenerator
{
// ReSharper disable InconsistentNaming
	public class IEnumerableGenerator
// ReSharper restore InconsistentNaming
	{
		public const string Path = @"C:\svn\linq-dimensions\LinqDimensions\";

		public string Generate(int from, int to)
		{
			var provider = new CSharpCodeProvider();
			var ns = new CodeNamespace("IllicitOnion.LinqDimensions");
			//TODO: Global import these, rather than in the namespace
			ns.Imports.Add(new CodeNamespaceImport("System"));
			ns.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));

			for (var numberOfArgs = from; numberOfArgs <= to; ++numberOfArgs)
			{
				var clazz = new CodeTypeDeclaration("IEnumerable") { IsInterface = true };
				for (var i = 0; i < numberOfArgs; ++i)
				{
					clazz.TypeParameters.Add("T" + i);
				}

				{
					var forEachMethod = new CodeMemberMethod {Name = "ForEach"};
					var actionParam = new CodeParameterDeclarationExpression
						{
							Name = "action",
							//TODO: This should *not* use string argument for generics!
							Type = new CodeTypeReference("Action" + GetGenericTypes(numberOfArgs))
						};
					forEachMethod.Parameters.Add(actionParam);
					clazz.Members.Add(forEachMethod);
				}

				for (var numberOfArgsToReturn = numberOfArgs + 1; numberOfArgsToReturn <= to; ++numberOfArgsToReturn)
				{
					var argsWithWhichToAnd = GetGenericTypes((numberOfArgsToReturn - numberOfArgs), false, numberOfArgs);

					var andMethod = new CodeMemberMethod { Name = "And" };
					var otherParam = new CodeParameterDeclarationExpression
					{
						Name = "other",
						//TODO: This should *not* use string argument for generics!
						Type = new CodeTypeReference("IEnumerable" + argsWithWhichToAnd)
					};
					andMethod.Parameters.Add(otherParam);
					for (var i = numberOfArgsToReturn - numberOfArgs; i < numberOfArgs; ++i)
					{
						andMethod.TypeParameters.Add("T" + (i + 1));
					}
					//TODO: This should *not* use string argument for generics!
					andMethod.ReturnType = new CodeTypeReference("IEnumerable" + GetGenericTypes(numberOfArgsToReturn));
					clazz.Members.Add(andMethod);
				}

				ns.Types.Add(clazz);
			}

			var writer = new StringWriter();
			//TODO: Tweak the options to make it pretty
			var options = new CodeGeneratorOptions {IndentString = "\t", BlankLinesBetweenMembers = false};
			provider.GenerateCodeFromNamespace(ns, writer, options);
			return writer.ToString();
		}

		public static string GetGenericTypes(int numberOfArgs, bool shouldBeOut=false, int startFrom=0)
		{
			var str = "<";
			for (var arg = 0; arg < numberOfArgs; ++arg)
			{
				if (arg != 0)
				{
					str += ", ";
				}
				if (shouldBeOut)
				{
					str += "out ";
				}
				str += "T" + (startFrom + arg);
			}
			str += ">";
			return str;
		}

		[Test]
		public void GenerateTest()
		{
			Console.WriteLine(Generate(2, 3));
		}

		[Test]
		public void SaveAsFile()
		{
			File.WriteAllText(Path + "IEnumerable.cs", Generate(2, 3));
		}
	}
}
